/**
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "GPUImageFilter.h"
#include "OpenGlUtils.h"
#include "napi_tool.h"
#include <GLES2/gl2ext.h>

const std::string GPUImageFilter::NO_FILTER_VERTEX_SHADER = R"delimiter(
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
    gl_Position = position;
    textureCoordinate = inputTextureCoordinate.xy;
}
)delimiter";

const std::string GPUImageFilter::NO_FILTER_FRAGMENT_SHADER = R"delimiter(
#extension GL_OES_EGL_image_external : require
varying highp vec2 textureCoordinate;
uniform samplerExternalOES inputImageTexture;
void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
)delimiter";

GPUImageFilter::GPUImageFilter()
    : vertexShader(NO_FILTER_VERTEX_SHADER),
      fragmentShader(NO_FILTER_FRAGMENT_SHADER) {}

GPUImageFilter::~GPUImageFilter() {}

GPUImageFilter::GPUImageFilter(const std::string vShader,
                               const std::string fShader)
{
    vertexShader = vShader;
    fragmentShader = fShader;
}

void GPUImageFilter::init()
{
    onInit();
    onInitialized();
}

void GPUImageFilter::onInit()
{
    glProgId = OpenGlUtils::loadProgram(vertexShader, fragmentShader);
    glAttribPosition = glGetAttribLocation(glProgId, "position");
    glUniformTexture = glGetUniformLocation(glProgId, "inputImageTexture");
    glAttribTextureCoordinate =
        glGetAttribLocation(glProgId, "inputTextureCoordinate");
    if (glProgId > 0) {
        initialized = true;
    }
}

void GPUImageFilter::onInitialized() {}

void GPUImageFilter::ifNeedInit()
{
    if (!initialized)
        init();
}

void GPUImageFilter::destroy()
{
    initialized = false;
    glDeleteProgram(glProgId);
    onDestroy();
}

void GPUImageFilter::onDestroy() {}

void GPUImageFilter::onOutputSizeChanged(int width, int height)
{
    outputWidth = width;
    outputHeight = height;
}

void GPUImageFilter::onDraw(int textureId, float *cubeBuffer,
                            float *textureBuffer)
{
    glUseProgram(glProgId);
    runPendingOnDrawTasks();
    if (!initialized)
        return;
    glVertexAttribPointer(glAttribPosition, ATTRIBUTE_SIZE_TWO, GL_FLOAT, false,
                          0, cubeBuffer);
    glEnableVertexAttribArray(glAttribPosition);
    glVertexAttribPointer(glAttribTextureCoordinate, ATTRIBUTE_SIZE_TWO, GL_FLOAT,
                          false, 0, textureBuffer);
    glEnableVertexAttribArray(glAttribTextureCoordinate);
    if (textureId != OpenGlUtils::NO_TEXTURE) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
        glUniform1i(glUniformTexture, 0);
    }
    onDrawArraysPre();
    glDrawArrays(GL_TRIANGLE_STRIP, 0, ATTRIBUTE_SIZE_FOUR);
    glDisableVertexAttribArray(glAttribPosition);
    glDisableVertexAttribArray(glAttribTextureCoordinate);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
}

void GPUImageFilter::onDrawArraysPre() {}

void GPUImageFilter::runPendingOnDrawTasks()
{
    std::unique_lock<std::mutex> lock(mutex);
    while (!tasks.empty()) {
        std::function<void()> task = std::move(tasks.front());
        tasks.pop();
        lock.unlock();
        task();
        lock.lock();
    }
}

void GPUImageFilter::runOnDraw(std::function<void()> task)
{
    std::unique_lock<std::mutex> lock(mutex);
    tasks.emplace(std::move(task));
}

bool GPUImageFilter::isInitialized() { return initialized; }

int GPUImageFilter::getOutputWidth() { return outputWidth; }

int GPUImageFilter::getOutputHeight() { return outputHeight; }

int GPUImageFilter::getProgram() { return glProgId; }

void GPUImageFilter::setInteger(int location, int intValue)
{
    runOnDraw([this, location, intValue]() {
        ifNeedInit();
        glUniform1i(location, intValue);
    });
}

void GPUImageFilter::setFloat(int location, float floatValue)
{
    runOnDraw([this, location, floatValue]() {
        ifNeedInit();
        glUniform1f(location, floatValue);
    });
}

void GPUImageFilter::setFloatVec2(int location, float *floatVec2)
{
    runOnDraw([this, location, floatVec2]() {
        ifNeedInit();
        glUniform2fv(location, 1, floatVec2);
    });
}

void GPUImageFilter::setFloatVec3(int location, float *floatVec3)
{
    runOnDraw([this, location, floatVec3]() {
        ifNeedInit();
        glUniform3fv(location, 1, floatVec3);
    });
}

void GPUImageFilter::setFloatVec4(int location, float *floatVec4)
{
    runOnDraw([this, location, floatVec4]() {
        ifNeedInit();
        glUniform4fv(location, 1, floatVec4);
    });
}

void GPUImageFilter::setFloatArray(int location, size_t count,
                                   float *floatArray)
{
    runOnDraw([this, location, count, floatArray]() {
        ifNeedInit();
        glUniform1fv(location, count, floatArray);
    });
}

void GPUImageFilter::setPoint(int location, float *point)
{
    runOnDraw([this, location, point]() {
        ifNeedInit();
        glUniform2fv(location, 1, point);
    });
}

void GPUImageFilter::setUniformMatrix3f(int location, float *matrix)
{
    runOnDraw([this, location, matrix]() {
        ifNeedInit();
        glUniformMatrix3fv(location, 1, false, matrix);
    });
}

void GPUImageFilter::setUniformMatrix4f(int location, float *matrix)
{
    runOnDraw([this, location, matrix]() {
        ifNeedInit();
        glUniformMatrix4fv(location, 1, false, matrix);
    });
}